//
//  structs.swift
//  swift_ example
//
//  Created by 魏熙美 on 2020/2/21.
//  Copyright © 2020 魏熙美. All rights reserved.
//

import Foundation

// 结构体是值类型
// 结构体，静态属性，计算属性，只读计算属性，属性观察者，属性包装，初始化器(构造函数/构造方法), 函数(方法)

// 定义结构体
struct SomeStruct {
    // 定义属性
    var width = 0
    var heigth = 0
    
    // 静态属性
    static var nums = 10
    // 静态属性可以直接 SomeStruct.nums
    
    // 定义个方法/函数， 并且有返回值
    func getWidth() -> Int {
        return self.width
    }
    
    // 结构体中想给 self 关键赋值，就必须把方法加上 mutating 关键字修饰
    mutating func setWidth(width: Int) {
        self.width = width
    }
    
    // 静态函数/方法
    static func getHeigth() {
        // 静态方法不能调用实例方法 和 成员
        // 只能调用静态的
        print(self.nums)
        
        // 调用方式
        // SomeStruct.getHeigth()
    }
}

// 定义结构体
struct FixeLengRange {
    // 定义属性
    // 如果定义属性的时候没有赋值，最好指定出是什么类型
    var firstValue: Int
    // 被创建后，这个值不允许被修改
    let length: Int
}

// 定义结构体，定义一个计算属性(类、结构体和枚举都可以定义计算属性) ===================================
// 这里用结构体定义
struct Rect {
    var origin = SomeStruct()
    
    // 定义计算属性（可以获取值 和 赋值）
    // 属性名为 center， 这个属性的返回值是 SomeStruct 类型
    var center: SomeStruct {
        // 获取值
        get {
            // 计算逻辑....
            
            // 最后需要返回一个 SomeStruct 类型
            return SomeStruct(width: origin.width * 10, heigth: origin.heigth * 10)
        }
        
        // 设置值
        // 这里的 newSomeStruct 类型就是 SomeStruct 类型
        // 如果没有指定 newSomeStruct 这个名称，默认名称就是 newValue
        set (newSomeStruct) {
            
            // 这里是赋值操作逻辑....
            
            // 最终值会赋值相应的属性
            origin.width = newSomeStruct.width * 2
            origin.heigth = newSomeStruct.heigth * 2
        }
    }
    
    // 只读计算属性，不能赋值
    var getSomeStructWidth: Int {
        get {
            // 读取值....
            
            return origin.width
        }
    }
    
}

// 定义结构体，属性观察者 ============================================
// willSet 会在该值被存储之前被调用。
// didSet 会在一个新值被存储后被调用。
struct StepCounter {
    // 定义一个属性，并设置属性观察者
    var totalSteps: Int = 0 {
        // 这个 totalSteps 属性，在要被赋值的是调用
        // 会在该值被存储之前被调用
        // 如果没有指定 newTotaStrps 这个名称，默认名称就是 newValue
        willSet(newTotaStrps) {
            print("totalSteps 准备复制成 \(newTotaStrps)")
        }
        
        // 会在一个新值被存储后被调用
        didSet {
           // 可以使用 oldValue 获取旧值
           // 值被赋值成功后，处理一些操作
           // 比如 新旧值比对
            if totalSteps > oldValue {
                print("totalSteps属性增加了\(totalSteps - oldValue)个值")
            }
        }
    }
}

// 定义一个属性包装(结构体、类、枚举都可以定义包装属性) =================================
// 使用结构体创建包装属性
@propertyWrapper // 标记为这是一个包装属性，就可以当成注解使用 @TwelveOrLess
struct TwelveOrLess {
    // 私有化属性
    private var number = 0
    
    // 计算属性
    // wrappedValue 属性名称是固定的，不能改变这个名称
    var wrappedValue: Int {
        get { return number }
        // 设置值的时候，保证值不会大于12
        set {
            // min 函数获取最小的那个值，第一个参数和第二个参数，谁小就返回谁
            number = min(newValue, 12)
        }
    }
    
    // Java语言称为构造器
    // 初试化器
    init() {
        // java里面是 this 关键字
        // self 关键字指的是当前的实例
        self.number = 1
    }
    
    init(wrappedValue: Int) {
        self.number = min(wrappedValue, 12)
    }
    
}

// 定义结构体，并调用上面的属性包装
struct SmalRectangle {
    // 给类型设置上包装属性
    @TwelveOrLess(wrappedValue: 10) var heigth: Int
    @TwelveOrLess var width: Int
    
    // 设置了包装属性后，这个属性在每次赋值取值的时候，都会经过包装属性的处理逻辑
    // 这个包装属性也就是 装饰者模式
}

// 定义结构体，并调用属性包装，可以不使用注解形式
struct SmalRectangle2 {
    // 直接私有属性上创建 包装属性
    // 这种方式的调用包装属性，TwelveOrLess 结构体就可以不用加上 @propertyWrapper 注解
    // private var _heigth = TwelveOrLess(wrappedValue: 10)
    private var _heigth = TwelveOrLess()
    var heigth: Int {
        // 直接调取包装属性的计算属性
        get { return _heigth.wrappedValue }
        // 把新值赋值给包装属性的计算属性
        set { _heigth.wrappedValue = newValue }
    }
}


func runStruct() {
    
    // 结构体的创建 ======================================
    
    // 创建结构体实例
    var someStruct = SomeStruct()
    print("someStruct高度：", someStruct.heigth)
    // 赋一个新值
    someStruct.width = 200
    
    // 创建的时候可以成员初始化
    var someStructTh = SomeStruct(width: 10, heigth: 20)
    print("someStructTh: ", someStructTh)
    someStructTh.heigth = 200
    
    
    // 创建时候可以初始化
    // 创建后，length 属性不允许被修改，因为是 let 类型
    var fixeLengRange = FixeLengRange(firstValue: 1, length: 10)
    print("fixeLengRange: ", fixeLengRange)
    fixeLengRange.firstValue = 2
    
    
    // 调用结构体的计算属性 =================================
    
    var rect = Rect(origin: SomeStruct(width: 10, heigth: 20))
    print("调用计算属性之前：\(rect.origin.heigth), \(rect.origin.width)")
    
    // 给计算属性赋值 ======================================
    rect.center = SomeStruct(width: 10, heigth: 20)
    print("调用计算属性后高度：", rect.center.heigth)
    print("调用计算属性后宽度：", rect.center.width)
    
    // 属性观察者 ==========================================
    var stepCounter = StepCounter()
    // 给这个属性赋值，会触发它的属性观察者
    stepCounter.totalSteps = 10
    
    // 包装属性 ============================================
    var smalRectangle = SmalRectangle()
    smalRectangle.heigth = 10
    // Prints "10"
    smalRectangle.width = 24 // 由于属性设置了包装属性，那么这个值不会超过数字 12
    // Prints "12"
    print("SmalRectangle使用了包装属性：", smalRectangle)
}

